using System.Collections.Generic;
using System.Diagnostics;

using OpenTK;

namespace ColladaDotNet.OpenTKMapping
{
	public class Skin : Controller
	{
		private Geometry geometry;

		private Matrix4d bind_shape_matrix;

		private SourceParam joints;

		private SourceParam invBindMatrix;

		private BindedSource jointOffset;

		private BindedSource weidhtOffset;

		private readonly List<BoneWeight[]> weights = new List<BoneWeight[]>();

		public Geometry Geometry
		{
			get
			{
				return geometry;
			}
		}

		public IList<BoneWeight[]> Weights
		{
			get
			{
				return weights;
			}
		}

		public SourceParam Joints
		{
			get
			{
				return joints;
			}
		}

		public SourceParam InvBindMatrix
		{
			get
			{
				return invBindMatrix;
			}
		}

		public Matrix4d BindShapeMatrix
		{
			get
			{
				return bind_shape_matrix;
			}
			set
			{
				bind_shape_matrix = value;
			}
		}

		internal override void Load(controller colladaData)
		{
			base.Load(colladaData);
			
			skin skinData = (skin)colladaData.Item;
			geometry = Document.GetElementByUrl<Geometry>(skinData.source1);
			BindShapeMatrix = Matrix.CreateMatrix4d(DoubleArrayConverter.ToArray(skinData.bind_shape_matrix), 0);
			foreach (InputLocal jointsInput in skinData.joints.input)
			{
				var i = Document.CreateElement<LocalInput>(jointsInput);
				switch (i.Semantic)
				{
					case "JOINT":
						joints = i.Source["JOINT"];
						break;
					case "INV_BIND_MATRIX":
						invBindMatrix = i.Source["TRANSFORM"];
						break;
				}
			}
			var vertex_weights = skinData.vertex_weights;
			count = vertex_weights.count;
			ulong stride = 1;
			foreach (InputLocalOffset jointsInput in vertex_weights.input)
			{
				var s = Document.CreateElement<BindedSource>(jointsInput);
				if (stride <= s.Offset)
					stride = s.Offset + 1;
				switch (jointsInput.semantic)
				{
					case "JOINT":
						jointOffset = s;
						break;
					case "WEIGHT":
						weidhtOffset = s;
						break;
				}
			}
			ulong pos = 0;
			// ((ulong)vertex_weights.vcount.Length + vertex_weights.count - 1) / vertex_weights.count;
			var vcounts = IntArrayConverter.ToArray(vertex_weights.vcount);
			var v = IntArrayConverter.ToArray(vertex_weights.v);
			Debug.WriteLine("vcounts.Length = " + vcounts.Length);
			Debug.WriteLine("v.Length = " + v.Length);

			foreach (var weightsCount in vcounts)
			{
				BoneWeight[] item = new BoneWeight[weightsCount];
				//Debug.WriteLine("Vertex with "+vcount+" bones");
				for (int i = 0; i < weightsCount; ++i)
				{
					BoneWeight item1 = new BoneWeight()
					{
						Index = (ulong)v[(int)(pos + jointOffset.Offset)],
						WeightIndex = (ulong)v[(int)(pos + weidhtOffset.Offset)]
					};
					item1.Weight = weidhtOffset.Source.GetDouble(item1.WeightIndex);
					item[i] = item1;
					pos += stride;
				}
				Weights.Add(item);
			}
		}
		SkinnedPositions skinnedPositions = new SkinnedPositions();

		private ulong count;

		public override void Render(Sceleton sceleton, BindMaterial material)
		{
			Utils.CheckError();

			base.Render(sceleton, material);

			if (!(Geometry is Mesh))
				return;

			var mesh = (Mesh)Geometry;
			ulong itemOffset = 0;

			Matrix4d[] bonePositions = new Matrix4d[Joints.Count];
			for (ulong i = 0; i < Joints.Count; ++i)
			{
				bonePositions[i] = BindShapeMatrix * InvBindMatrix.GetMatrix(i) * sceleton.GetBonePosition(Joints.GetName(i));
			}
			var vertices = mesh.VerticesPositions.Elements;
			if (skinnedPositions.NumPositions != vertices)
			{
				skinnedPositions.Positions = new Vector3[vertices];
			}

			for (ulong vertexIndex = 0; vertexIndex < (ulong)vertices; vertexIndex++)
			{
				var ints = Weights[(int)(vertexIndex)];

				var position = mesh.VerticesPositions.GetVector3d(vertexIndex);

				double sum = 0;
				var v = new Vector3d(0, 0, 0);
				foreach (var i in ints)
				{
					var d = i.Weight;
					var tr = Vector3d.Transform(position, bonePositions[i.Index]);
					v.X += d * tr.X;// / tr.W;
					v.Y += d * tr.Y;// / tr.W;
					v.Z += d * tr.Z;// / tr.W;
					sum += d;
				}
				v = v / sum;
				var res = v;
				skinnedPositions.Positions[vertexIndex] = new Vector3((float)(res.X), (float)(res.Y), (float)(res.Z));
			}
			for (int itemIndex = 0; itemIndex < mesh.Items.Count; itemIndex++)
			{
				var item = mesh.Items[itemIndex];

				var m = material.Bind(item.Material);
				item.Render(skinnedPositions);
				Utils.CheckError();
				m.Unbind();
				itemOffset += item.NumPositions;
			}
		}
	}
}